#ifndef PUDGE_META_LAZY_HPP
#define PUDGE_META_LAZY_HPP

#include <pudge/meta/tuple.hpp>

#include <boost/preprocessor/iteration/iterate.hpp>
#include <boost/preprocessor/repetition/enum.hpp>
#include <boost/preprocessor/repetition/enum_params.hpp>

#ifndef PUDGE_META_LAZY_PARAMS
#	define PUDGE_META_LAZY_PARAMS 7
#endif

/// Lazy function implementation, allows you to easily bind arguments
/// to functions creating new functions with a smaller arity.

/// Thanks to Joel De Guzman for his work on boost::phoenix, although
/// my code is quite different than his my interface is the same, making
/// this a lighter alternative to some of the boost libraries.  Note that no
/// lambda specializations exist ie. f(_1 + x)

namespace pudge { namespace meta {

	template<int I> struct arg { };

#define PUDGE_META_LAZY_M(Z,N,A) const arg<N> BOOST_PP_CAT(_,BOOST_PP_INC(N));
	BOOST_PP_REPEAT(PUDGE_META_LAZY_PARAMS,PUDGE_META_LAZY_M,);
#undef PUDGE_META_LAZY_M

	struct null_type {};

	template<class T> struct ref_
	{
		typedef T value_type;
		T & value;
		ref_(T & value_) : value(value_) {} 
	};

	template<class T> struct val_
	{
		typedef T value_type;
		T value;
		val_(T value_) : value(value_) {} 
	};

	template<class T> ref_<T> ref(T & t) { return ref_<T>(t); }
	template<class T> val_<T> val(T t) { return val_<T>(t); }

	template<class T> struct unref { typedef T type; };
	template<class T> struct unref<T&> { typedef T type; };

	template<class Function> struct lazy
	{
		typedef Function function_type;
		Function function_;

		lazy(Function const& function__) : function_(function__) {}

		template<class Element, class Args> struct extract
		{
			typedef Element& result_type;
			Element & operator()(Element & e, Args & a) const
			{
				return e;
			}
		};
		template<int I, class Args> struct extract<const arg<I>,Args>
		{
			typedef typename tuple_element<I>::result<Args>::type result_type;
			result_type operator()(const arg<I> &, Args & a) const
			{
				return tuple_element<I>()(a);
			}
		};

		template<class Stored, class Args> struct call {};

		template<class Tuple> struct bound
		{
			Function function_;
			Tuple tuple_;

			bound(Function & function__, Tuple & tuple__)
				:	function_(function__) 
				,	tuple_(tuple__) {}

			bound(Function const& function__, Tuple & tuple__)
				:	function_(function__) 
				,	tuple_(tuple__) {}

#define PUDGE_META_LAZY_M(Z,N,A) class BOOST_PP_CAT(T,N) = null_tuple_element
			template<BOOST_PP_ENUM(PUDGE_META_LAZY_PARAMS,PUDGE_META_LAZY_M,)>
#undef PUDGE_META_LAZY_M
			struct result
			{
				typedef typename Function::result<BOOST_PP_ENUM_PARAMS(PUDGE_META_LAZY_PARAMS,T)>::type type;
			};

#define PUDGE_META_LAZY_CTUPLE(Z,N,D) T ## N&
#define PUDGE_META_LAZY_CARGS(Z,N,D) T ## N & t ## N
			template<BOOST_PP_ENUM_PARAMS(PUDGE_META_LAZY_PARAMS, class T)>
			typename call<Tuple,tuple<BOOST_PP_ENUM(PUDGE_META_LAZY_PARAMS,PUDGE_META_LAZY_CTUPLE,)>>::result_type
				operator()(BOOST_PP_ENUM(PUDGE_META_LAZY_PARAMS,PUDGE_META_LAZY_CARGS,))
			{
				typedef tuple<BOOST_PP_ENUM(PUDGE_META_LAZY_PARAMS,PUDGE_META_LAZY_CTUPLE,)> args_type;
				return call<Tuple,args_type>()
					(	function_
					,	tuple_
					,	args_type(BOOST_PP_ENUM_PARAMS(PUDGE_META_LAZY_PARAMS, t)));
			}
#undef PUDGE_META_LAZY_CTUPLE
#undef PUDGE_META_LAZY_CARGS

			typename call<Tuple,tuple<void>>::result_type operator()()
			{
				typedef tuple<void> args_type;
				return call<Tuple,args_type>()(function_, tuple_, args_type());
			}

#define BOOST_PP_FILENAME_1 "pudge/meta/detail/unroll_lazy_call.hpp"
#define BOOST_PP_ITERATION_LIMITS (1,BOOST_PP_DEC(PUDGE_META_LAZY_PARAMS))
#include BOOST_PP_ITERATE()
		};

		template<class T> struct param 
		{
			typedef T type;
			T & operator()(T & v) { return v; }
		};
		template<class T> struct param<val_<T>>
		{
			typedef T type;
			T operator()(val_<T> & v) { return v.value; }
		};
		template<class T> struct param<ref_<T>>
		{
			typedef T& type;
			T & operator()(ref_<T> & v) { return v.value; }
		};

#define BOOST_PP_FILENAME_1 "pudge/meta/detail/unroll_lazy.hpp"
#define BOOST_PP_ITERATION_LIMITS (1,PUDGE_META_LAZY_PARAMS)
#include BOOST_PP_ITERATE()
	};
}}

#endif